﻿package com.google.code.p.ivans.applications;

/*
 * Параллельное перемножение 2 статических матриц
 */
public class Application_7_Threads {
	
	public static double[][] matrixA;
	public static double[][] matrixB;
	public static double[][] matrixRez;	
	
	public static void createMatrix(int rowCountToMatrixA,int columnCountToMatrixA, int rowCountToMatrixB,int columnCountToMatrixB) {
		
		matrixA = new double[rowCountToMatrixA][columnCountToMatrixA];
		matrixB = new double[rowCountToMatrixB][columnCountToMatrixB];		
		matrixRez = new double[rowCountToMatrixA][columnCountToMatrixB]; 
		
		randomFilling();
	}
	
	public static void randomFilling() {
		for(int i = 0; i < matrixA.length; i++) {
			for(int j = 0; j < matrixA[i].length; j++) {
				matrixA[i][j] = (int)(Math.random() * 10);
				
			}
		}
		
		for(int i = 0; i < matrixB.length; i++) {
			for(int j = 0; j < matrixB[i].length; j++) {
				matrixB[i][j] = (int)(Math.random() * 10);			
			}
		}		
	}
	
	/*  
	 * отдельный метод для присваивания вычисленного значения ячейки
	 * нужен для однозначного вывода, какой именно поток получил это значение
	 * т.е. если у класса ParallelMultiplier вызвать
	 *  
	 *  Application_7_Thread.matrixRez[row][column] = rez;
	 *  
	 *  System.out.println(Thread.currentThread.getName() + "вычислил значение" + value + "для ячейки " + row + ", "+ column);
	 *  
	 *  поток может прерваться перед System.out... и Thread.currentThread.getName() даст имя другого потока
	*/
	public static void setElementForResultMatrix(int row, int column, double value, String threadName)  {
		matrixRez[row][column] = value;		
		System.out.println(threadName + " вычислил значение " + value + " для ячейки [" + row + "," + column + "]"); 
	}

	
	public static void main(String[] args) {
		
		// количество потоков
		int threadsAmount = 10;
		Thread[] threads = new Thread[threadsAmount];
		
		// создаем матрицы		
		int rowsA = 5;
		int columnsA = 3;
		int rowsB = 3;
		int columnsB = 5;
		
		createMatrix(rowsA, columnsA, rowsB, columnsB);
		
		//сумма элементов в результирующей таблице
		int elementsAmountInRezultMatrix =  rowsA * columnsB;
		
		//сколько элементов достанутся одному
		int elementsAmountForOneThread = elementsAmountInRezultMatrix / threadsAmount;
		
		//остаток распределится постепенно, прибавляя к каждому новому потоку по 1 элементу из остатка
		int rest = elementsAmountInRezultMatrix % threadsAmount;
		
		/* 
		 *  указатель на начало последовательности элементов новому потоку
		 *  потоки будут работать с результирующей матрицей как с одномерным массивом
		 *  если результирующая матрица 10*10, т.е 100 элементов
		 *  то например 7 потоков будут получать:
		 *  1 поток - указатель=0, количество=((100 / 7)=14) + 1 //единица прибавляется до тех пор пока есть остаток (100 % 7) == (2 - 1) >= 0 -> true
		 *  2 поток - указатель=15, количество=14 + 1 // (2 - 2) >= 0 -> true
		 *  3 поток - указатель=30, количество=14 // (2 - 3) >= 0 -> false
		 *  4 поток - указатель=44, количество=14 // (2 - 4) >= 0 -> false
		 *  .....		 *  
		 */
		int counter = 0;
		
		// создаем потоки
		for(int i = 0; i < threadsAmount; i++) {
			
			int elementsAmoung;
			
			if(rest-- > 0) {
				elementsAmoung = elementsAmountForOneThread + 1;
			} 
			else {
				elementsAmoung = elementsAmountForOneThread;
			}
			
			threads[i] = new Thread(new ParallelMultiplier(counter, elementsAmoung));
			
			counter += elementsAmoung;
			
			//сработает, если число потоков больше числа элементов в результирующей матрице
			if(counter >= elementsAmountInRezultMatrix)
				break;
		}		
			
		// запуск
		for(int i = 0; i < threadsAmount && threads[i] != null; i++) {
			threads[i].start();
		}
		
		//пока все не отработают, main поток спит 
		while(Thread.activeCount() > 1) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		System.out.println("----------Matrix A--------------");
		for(int i = 0; i < matrixA.length; i++) {
			System.out.print("[");
			for(int j = 0; j < matrixA[i].length; j++) {
				System.out.printf(" [%.0f] ", matrixA[i][j]);				
			}
			System.out.println("]");
		}
		
		System.out.println("----------Matrix B--------------");
		for(int i = 0; i < matrixB.length; i++) {
			System.out.print("[");
			for(int j = 0; j < matrixB[i].length; j++) {
				System.out.printf(" [%.0f] ", matrixB[i][j]);				
			}
			System.out.println("]");
		}
		
		
		System.out.println("----------Matrix Rez--------------");
		for(int i = 0; i < matrixRez.length; i++) {
			System.out.print("[");
			for(int j = 0; j < matrixRez[i].length; j++) {
				System.out.printf(" [%.0f] ", matrixRez[i][j]);				
			}
			System.out.println("]");
		}
	}
}

class ParallelMultiplier implements Runnable {
	
	private static int rows;
	private static int columns;
	private static int commonSize;
	
	private int point;
	private int amount;	
	
	static {
		rows = Application_7_Threads.matrixRez.length;
		columns = Application_7_Threads.matrixRez[0].length;
		commonSize = Application_7_Threads.matrixB.length;
	}
	
	
	public ParallelMultiplier() {}
	
	public ParallelMultiplier(int point, int amount) {
		this.point = point;
		this.amount = amount;			
	}

	@Override
	public void run() {
		for(int i = 0; i < amount; i++) {
			//по указателю как бы одномерного массива получаем индекс строки и столбца двумерного массива		
			multiply((point + i) / columns, (point + i) % columns);
		}
	}
	
	public void multiply(int rowIndex, int columnIndex) {		
		double rez = 0;
		
		for(int i = 0; i < commonSize; i++) {
			rez += Application_7_Threads.matrixA[rowIndex][i] * Application_7_Threads.matrixB[i][columnIndex];
		}
		
		Application_7_Threads.setElementForResultMatrix(rowIndex, columnIndex, rez, Thread.currentThread().getName());		
	}
}


